/**
 * 
 */
package com.ubo.transform.java;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import com.ubo.graphviz.model.graph.Graph;
import com.ubo.graphviz.model.graph.GraphVizModel;
import com.ubo.graphviz.model.graph.statement.Attribute;
import com.ubo.graphviz.model.graph.statement.AttributeList;
import com.ubo.graphviz.model.graph.statement.EdgeStatementNode;
import com.ubo.graphviz.model.graph.statement.IStatement;
import com.ubo.graphviz.model.graph.statement.node.NodeFormatter;
import com.ubo.graphviz.model.graph.statement.node.NodeStmt;
import com.ubo.graphviz.model.graph.statement.node.classNode.AttributElement;
import com.ubo.graphviz.model.graph.statement.node.classNode.ClassStmt;
import com.ubo.graphviz.model.graph.statement.node.classNode.IClassElement;
import com.ubo.graphviz.model.graph.statement.node.classNode.MethodElement;
import com.ubo.java.associations.Association;
import com.ubo.java.associations.AssociationManager;
import com.ubo.java.associations.Card;
import com.ubo.java.associations.EAssociationCard;
import com.ubo.java.associations.EAssociationType;
import com.ubo.java.model.EModifier;
import com.ubo.java.model.JavaModel;
import com.ubo.java.model.PackageModel;
import com.ubo.java.model.classifier.ArrayType;
import com.ubo.java.model.classifier.ClassModel;
import com.ubo.java.model.classifier.EPrimitiveTypes;
import com.ubo.java.model.classifier.EnumClassifier;
import com.ubo.java.model.classifier.ExistingClassifier;
import com.ubo.java.model.classifier.IClassifier;
import com.ubo.java.model.classifier.Interface;
import com.ubo.java.model.comment.Comment;
import com.ubo.java.model.declaration.Attribut;
import com.ubo.java.model.declaration.Parameter;
import com.ubo.java.model.declaration.Variable;
import com.ubo.java.model.methode.Constructor;
import com.ubo.java.model.methode.Method;
import com.ubo.java.model.methode.Prototype;
import com.ubo.transform.ITransformConstants;
import com.ubo.util.Duo;
import com.ubo.visu.model.EEntityType;

/**
 * @author saludegu
 * 
 */
public class JavaGraphvizTransformer extends AJavaVisitor<IStatement> implements
		ITransformConstants {

	/**
	 * Map stockant les nodes en fonction de leur nom.
	 */
	protected Map<String, NodeStmt> mMapNodes;

	/**
	 * Pile des elements d'une classe. Utilisé pour remplir les attributs d'un
	 * Node.
	 */
	protected Stack<IClassElement> mStackElements;

	/**
	 * Modèle contenant les éléments du modèle GraphViz.
	 */
	protected GraphVizModel mGraphVizModel;

	/**
	 * Graphe correspondant au modèle JAVA.
	 */
	protected Graph mGraph;

	/**
	 * Gestionnaire des associations.
	 */
	protected AssociationManager mAssociationManager;

	/**
	 * Liste de statements.
	 */
	protected List<IStatement> mListStatements;

	/**
	 * 
	 * Constructor
	 * 
	 * @param javaModel
	 */
	public JavaGraphvizTransformer() {
		super();
		mStackElements = new Stack<IClassElement>();
		mMapNodes = new HashMap<String, NodeStmt>();
		mListStatements = new ArrayList<IStatement>();
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.classifier.ClassModel)
	 */
	@Override
	public void generate(ClassModel classModel) {
		// Création d'un node
		ClassStmt newNode = new ClassStmt(classModel.getClassifierName(),
				mAssociationManager.giveId(classModel), EEntityType.CLASS);
		NodeFormatter.formatNode(newNode);
		// Création des attributs
		if (classModel.getAttributSet() != null
				&& classModel.getAttributSet().size() > 0)
			for (Attribut attr : classModel.getAttributSet()) {
				if (!attr.getType().generationNeeded()) {
					attr.generate(this);
					newNode.addAllAttribute(mStackElements.pop()
							.getAttributsFromElement().getAttributes());
				}
			}
		// Création des méthodes
		if (classModel.getMethodeSet() != null
				&& classModel.getMethodeSet().size() > 0)
			for (Method meth : classModel.getMethodeSet()) {
				meth.generate(this);
				newNode.addAllAttribute(mStackElements.pop()
						.getAttributsFromElement().getAttributes());
			}
		// Création des associations
		// Extends
		if (classModel.getExtendedClasse() != null)
			mAssociationManager.addAssociation(
					new Duo<Integer, IClassifier>(mAssociationManager
							.giveId(classModel), classModel),
					new Duo<Integer, IClassifier>(mAssociationManager
							.giveId(classModel.getExtendedClasse()), classModel
							.getExtendedClasse()), EAssociationType.EXTENDS,
					new Card(EAssociationCard.UN, EAssociationCard.UN),
					new Card(EAssociationCard.UN, EAssociationCard.UN));
		// Implements
		if (classModel.getInterfaceImplemented() != null
				&& classModel.getInterfaceImplemented().size() > 0)
			for (Interface i : classModel.getInterfaceImplemented())
				mAssociationManager.addAssociation(
						new Duo<Integer, IClassifier>(mAssociationManager
								.giveId(classModel), classModel),
						new Duo<Integer, IClassifier>(mAssociationManager
								.giveId(i), i), EAssociationType.IMPLEMENTS,
						new Card(EAssociationCard.UN, EAssociationCard.UN),
						new Card(EAssociationCard.UN, EAssociationCard.UN));
		mMapNodes.put(classModel.getClassifierName(), newNode);
		mListStatements.add(newNode);
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.classifier.EPrimitiveTypes)
	 */
	@Override
	public void generate(EPrimitiveTypes ePrimitiveTypes) {
		Attribute newAttr = new Attribute(TYPE_ATTRIBUTE_NAME,
				ePrimitiveTypes.toString());
		addToStack(newAttr);
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.classifier.ExistingClassifier)
	 */
	@Override
	public void generate(ExistingClassifier existingClassifier) {
		Attribute newAttr = new Attribute(TYPE_ATTRIBUTE_NAME,
				existingClassifier.getClassifierName());
		addToStack(newAttr);
	}

	/**
	 * Utile lorsque le type de l'attribut est un type primitif ou un type non
	 * généré, sinon, on crée une association entre les deux classes.
	 * 
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.declaration.Attribut)
	 */
	@Override
	public void generate(Attribut attribut) {
		AttributElement newAttr = new AttributElement();
		StringBuffer buffer = new StringBuffer();
		buffer.append(attribut.getVisibility().toString() + " ");
		if (attribut.getModifier() != null && attribut.getModifier().size() > 0)
			for (EModifier modifier : attribut.getModifier())
				buffer.append(modifier.toString() + " ");
		buffer.append(attribut.getType().toString() + " ");
		buffer.append(attribut.getName());
		newAttr.addAttribute(new Attribute(ATTRIBUTE_TYPE_VALUE_ATTRIBUTE,
				buffer.toString()));
		mStackElements.addElement(newAttr);
	}

	/**
	 * Utile lorsque le type de l'attribut est un type primitif ou un type non
	 * généré, sinon, on crée une association entre les deux classes.
	 * 
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.declaration.Variable)
	 */
	@Override
	public void generate(Variable variable) {
		// On ne gere par les variables dans les graphes.
	}

	/**
	 * Utile lorsque le type de l'attribut est un type primitif ou un type non
	 * généré, sinon, on crée une association entre les deux classes.
	 * 
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.declaration.Parameter)
	 */
	@Override
	public void generate(Parameter parameter) {
		AttributElement newAttr = new AttributElement();
		StringBuffer buffer = new StringBuffer();
		if (parameter.getModifier() != null
				&& parameter.getModifier().size() > 0)
			for (EModifier modifier : parameter.getModifier())
				buffer.append(modifier.toString() + " ");
		buffer.append(parameter.getType().toString() + " ");
		buffer.append(parameter.getName());
		newAttr.addAttribute(new Attribute(ATTRIBUTE_TYPE_VALUE_ATTRIBUTE,
				buffer.toString()));
		mStackElements.addElement(newAttr);
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.methode.Constructor)
	 */
	@Override
	public void generate(Constructor constructor) {
		MethodElement newMethod = new MethodElement();
		AttributeList newList = new AttributeList();
		newList.addAttribute(new Attribute(ATTRIBUTE_TYPE_NAME,
				ATTRIBUTE_TYPE_VALUE_METHOD));
		newList.addAttribute(new Attribute(VISIBILITY_ATTRIBUTE_NAME,
				constructor.getVisibility().toString()));
		newList.addAttribute(new Attribute(TYPE_ATTRIBUTE_NAME, constructor
				.getType().toString()));
		if (constructor.getParameterList() != null
				&& constructor.getParameterList().size() > 0)
			for (Parameter param : constructor.getParameterList()) {
				param.generate(this);
				newMethod.addAttributeElement(mStackElements.pop());
			}
		newMethod.addAllAttributes(newList.getAttributes());
		mStackElements.push(newMethod);
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.methode.Prototype)
	 */
	@Override
	public void generate(Prototype prototype) {
		MethodElement newMethod = new MethodElement();
		StringBuffer buffer = new StringBuffer();
		buffer.append(prototype.getVisibility().toString() + " ");
		if (prototype.getModifierList() != null
				&& prototype.getModifierList().size() > 0)
			for (EModifier modifier : prototype.getModifierList())
				buffer.append(modifier.toString() + " ");
		buffer.append(prototype.getType().toString() + " ");
		buffer.append(prototype.getName());
		if (prototype.getParameterList() != null
				&& prototype.getParameterList().size() > 0) {
			buffer.append("(");
			for (Parameter param : prototype.getParameterList()) {
				param.generate(this);
				buffer.append(mStackElements.pop().toString());
			}
			buffer.append(")");
		}
		newMethod.addAttribute(new Attribute(ATTRIBUTE_TYPE_VALUE_METHOD,
				buffer.toString()));
		mStackElements.push(newMethod);
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.methode.Method)
	 */
	@Override
	public void generate(Method method) {
		MethodElement newMethod = new MethodElement();
		StringBuffer buffer = new StringBuffer();
		buffer.append(method.getVisibility().toString() + " ");
		if (method.getModifierList() != null
				&& method.getModifierList().size() > 0)
			for (EModifier modifier : method.getModifierList())
				buffer.append(modifier.toString() + " ");
		buffer.append(method.getType().toString() + " ");
		buffer.append(method.getMethodName());
		if (method.getParameterList() != null
				&& method.getParameterList().size() > 0) {
			buffer.append("(");
			for (Parameter param : method.getParameterList()) {
				param.generate(this);
				buffer.append(mStackElements.pop().toString());
			}
			buffer.append(")");
		}
		newMethod.addAttribute(new Attribute(ATTRIBUTE_TYPE_VALUE_METHOD,
				buffer.toString()));
		mStackElements.push(newMethod);
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.PackageModel)
	 */
	@Override
	public void generate(PackageModel packageModel) {
		// TODO Packages
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.classifier.EnumClassifier)
	 */
	@Override
	public void generate(EnumClassifier enumClassifier) {
		// TODO Enums
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.classifier.Interface)
	 */
	@Override
	public void generate(Interface interFace) {
		// Création d'un node
		ClassStmt newNode = new ClassStmt(interFace.getClassifierName(),
				mAssociationManager.giveId(interFace), EEntityType.INTERFACE);
		NodeFormatter.formatNode(newNode);
		// Création des attributs
		if (interFace.getAttributs() != null
				&& interFace.getAttributs().size() > 0)
			for (Attribut attr : interFace.getAttributs()) {
				if (!attr.getType().generationNeeded()) {
					attr.generate(this);
					newNode.addAllAttribute(mStackElements.pop()
							.getAttributsFromElement().getAttributes());
				}
			}
		// Création des méthodes
		if (interFace.getPrototypes() != null
				&& interFace.getPrototypes().size() > 0)
			for (Prototype proto : interFace.getPrototypes()) {
				proto.generate(this);
				newNode.addAllAttribute(mStackElements.pop()
						.getAttributsFromElement().getAttributes());
			}
		// Création des associations
		// Extends
		if (interFace.getInterfaceImplemented() != null
				&& interFace.getInterfaceImplemented().size() > 0)
			for (Interface inter : interFace.getInterfaceImplemented()) {
				mAssociationManager.addAssociation(
						new Duo<Integer, IClassifier>(mAssociationManager
								.giveId(interFace), interFace),
						new Duo<Integer, IClassifier>(mAssociationManager
								.giveId(inter), inter),
						EAssociationType.EXTENDS, new Card(EAssociationCard.UN,
								EAssociationCard.UN), new Card(
								EAssociationCard.UN, EAssociationCard.UN));
			}
		mMapNodes.put(interFace.getClassifierName(), newNode);
		mListStatements.add(newNode);
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.classifier.ArrayType)
	 */
	@Override
	public void generate(ArrayType arrayType) {
		// TODO ArrayType
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.comment.Comment)
	 */
	@Override
	public void generate(Comment comment) {
		// TODO Commentaires.
	}

	/**
	 * Genere les associations contenues dans le manager.
	 */
	public void generateAssociations() {
		for (Association association : mAssociationManager.getAssociations()) {
			EdgeStatementNode edgeNode = new EdgeStatementNode(association
					.getAssociation().getA(), association.getAssociation()
					.getB());
			edgeNode.addAttribute(new Attribute("assoDesc", association
					.getCard1().getCard().getA()
					+ ","
					+ association.getCard1().getCard().getB()
					+ ":"
					+ association.getCard2().getCard().getA()
					+ ","
					+ association.getCard2().getCard().getB()
					+ ":"
					+ association.getAssociationType().toString()));
			mListStatements.add(edgeNode);
		}
	}

	/**
	 * Getter de la liste des stmts générés.
	 * 
	 * @return
	 */
	public List<IStatement> getStatements() {
		return mListStatements;
	}

	/**
	 * @see com.ubo.transform.java.IJavaVisitor#generate(com.ubo.java.model.JavaModel)
	 */
	@Override
	public void generate(JavaModel javaModel) {
		mAssociationManager = javaModel.getAssociationManager();
		for (IClassifier classifier : javaModel.getClassifiers()) {
			classifier.generate(this);
		}
		generateAssociations();
	}
}
